package in;

import general.Killable;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;

import javax.xml.bind.DataBindingException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Поток-сервер, слушающий порт. Создает потоки Worker для обработки запросов.
 * 
 * @author evilenka@yandex.ru
 * 
 */

public class Daemon extends Thread implements Killable
{

	private static final Logger logger = LoggerFactory.getLogger(Daemon.class);
	private Properties configs;
	private int port;
	private ServerSocket server;
	private ArrayList<Worker> workers;

	private volatile boolean killed = false;
	private long ttl;

	/**
	 * Конструктор.
	 * 
	 * @param configs
	 *            - конфиги, посланные из Веба
	 * @throws DaemonException
	 *             если нет соответствующих конфигов или нельзя забиндиться
	 */

	public Daemon(Properties configs) throws DaemonException
	{
		this.configs = configs;
		if (configs.getProperty("in.port") != null)
		{
			try
			{
				this.port = Integer.parseInt(configs.getProperty("in.port"));
			} catch (NumberFormatException e)
			{
				String msg = "Error in config: property 'in.port' must be integer";
				logger.error(msg);
				throw new DataBindingException(msg, e);

			}
		} else
		{
			logger.error("No such property 'in.port' in configurations");
			throw new DaemonException(
					"Error in configs! Property 'in.port' required!");
		}
		try
		{
			server = new ServerSocket(port);
		} catch (IOException e)
		{
			throw new DaemonException(
					"Error! Could not create ServerSocket on port " + port);
		}
		this.workers = new ArrayList<Worker>();
	}

	/**
	 * Старт потока, выполняется Вебом.
	 * 
	 * 
	 */

	@Override
	public void run()
	{
		while (true)
		{
			if (killed)
			{
				for (Worker w : workers)
				{
					w.kill(ttl / 2);
				}
				try
				{
					sleep(ttl / 2);
				} catch (InterruptedException e1)
				{

				}
				for (Worker w : workers)
				{
					if (w.isAlive())
					{
						w.stop();
					}
				}
				try
				{
					server.close();
				} catch (IOException e)
				{
					logger.warn("Daemon could not close socket. "
							+ e.getLocalizedMessage());
				}
				return;
			}
			try
			{
				Socket client = server.accept();
				InetAddress ip = client.getInetAddress();
				Worker worker = new Worker(client, configs);
				workers.add(worker);
				logger.info("New worker for client " + ip.getHostAddress()
						+ " started");
			} catch (IOException e)
			{
				logger.warn("Daemon server could not accept client. "
						+ e.getLocalizedMessage());
			} catch (DaemonException e)
			{
				logger.warn("Daemon server could not create Worker. "
						+ e.getLocalizedMessage());
			}

		}

	}

	/**
	 * Убийство.
	 * 
	 * @param milliseconds
	 *            время до принудительной остановки
	 */

	@Override
	public void kill(long milliseconds)
	{
		killed = true;
		ttl = milliseconds;
	}

}
